﻿using System;
using System.Collections.Generic;
using T0yK4T.IO;
using System.ComponentModel;
using ProtoBuf;

namespace ToyChat.BuiltinModules
{
    /// <summary>
    /// An implementation of <see cref="IChannel"/> that can be used to remotely manage channels
    /// </summary>
    [ProtoContract(Name = "RemoteChannel")]
    public class RemoteChannel : IChannel
    {
        private Dictionary<int, IUser> users = new Dictionary<int, IUser>();

        /// <summary>
        /// Initializes a new empty instance of <see cref="RemoteChannel"/>
        /// </summary>
        public RemoteChannel()
        {
        }

        /// <summary>
        /// Initializes a new instance of <see cref="RemoteChannel"/> and copies the values of the specified <see cref="IChannel"/> to the newly created instance
        /// </summary>
        /// <param name="template">The <see cref="IChannel"/> to use as a template</param>
        public RemoteChannel(IChannel template)
        {
            this.Reload(template);
        }

        /// <summary>
        /// Reloads all informations available in the specified <see cref="IChannel"/>
        /// </summary>
        /// <param name="template">The <see cref="IChannel"/> to use as a template</param>
        public void Reload(IChannel template)
        {
            this.users.Clear();
            foreach (IUser user in template.Users)
                this.users.Add(user.UserID, user);
            this.ChannelID = template.ChannelID;
            this.ChannelName = template.ChannelName;
            this.MotD = template.MotD;
            this.IsHidden = template.IsHidden;
            this.OnReloaded();
        }

        /// <summary>
        /// Called when the channel has finished reloading
        /// </summary>
        private void OnReloaded()
        {
            if (this.Reloaded != null)
                this.Reloaded.Invoke();
        }

        /// <summary>
        /// NYI
        /// </summary>
        /// <param name="user"></param>
        public void AddUser(IUser user)
        {
            
        }

        private Core core;
        /// <summary>
        /// Gets or Sets a reference to the <see cref="Core"/> that received this channel
        /// </summary>
        public Core Core
        {
            get { return this.core; }
            set
            {
                if (this.core != null)
                    this.core.UserUpdated -= new UserUpdatedHandler(core_UserUpdated);
                this.core = value;
                if (this.core != null)
                    this.core.UserUpdated += new UserUpdatedHandler(core_UserUpdated);
            }
        }

        /// <summary>
        /// Handler for <see cref="ToyChat.BuiltinModules.Core.UserUpdated"/>
        /// </summary>
        /// <param name="oldUser">The old <see cref="IUser"/> object that is being replaced</param>
        /// <param name="newUser">The new <see cref="IUser"/> object that replaces the old one</param>
        void core_UserUpdated(IUser oldUser, IUser newUser)
        {
            if (this.users.ContainsKey(oldUser.UserID))
            {
                if (newUser == null)
                    this.users.Remove(oldUser.UserID);
                else
                    this.users[oldUser.UserID] = newUser;
                this.OnUserUpdated(oldUser, newUser);
            }
        }

        /// <summary>
        /// Called when a user has been updated with a new copy from the server
        /// </summary>
        /// <param name="oldUser">The old <see cref="IUser"/> object that is being replaced</param>
        /// <param name="newUser">The new <see cref="IUser"/> object that replaces the old one</param>
        private void OnUserUpdated(IUser oldUser, IUser newUser)
        {
            if (this.UserUpdated != null)
                this.UserUpdated.Invoke(oldUser, newUser);
        }

        /// <summary>
        /// Gets or Sets the ID of this channel
        /// </summary>
        [ProtoMember(1)]
        public int ChannelID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or Sets the name of this channel
        /// </summary>
        [ProtoMember(2)]
        public string ChannelName
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or Sets the MotD (Message of the Day) of this channel
        /// </summary>
        [ProtoMember(3)]
        public string MotD
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or Sets a value indicating wether or not this channel is hidden
        /// </summary>
        [ProtoMember(4)]
        public bool IsHidden
        {
            get;
            set;
        }

        /// <summary>
        /// Fires the Reloaded event (used when a channel is modified externally)
        /// </summary>
        internal void TriggerReload()
        {
            this.OnReloaded();
        }


        /// <summary>
        /// Used simply as a "front" for transferring userobjects accross the network using ProtoBuf
        /// </summary>
        [ProtoMember(5)]
        public RemoteUser[] TransferUsers
        {
            get
            {
                List<RemoteUser> lst = new List<RemoteUser>();
                foreach (IUser user in this.users.Values)
                    lst.Add(new RemoteUser(user));
                return lst.ToArray();
            }
            set
            {
                this.users.Clear();
                foreach (IUser user in value)
                    this.users.Add(user.UserID, user);
            }
        }

        /// <summary>
        /// Gets or Sets the password of this channel (This is currently not implemented)
        /// </summary>
        public string Password
        {
            get;
            set;
        }

        /// <summary>
        /// Gets the number of users currently residing in this channel
        /// </summary>
        public int UserCount
        {
            get { return this.users.Count; }
        }

        /// <summary>
        /// Attempts to remove the specified user from this channel
        /// </summary>
        /// <param name="user">The user to remove</param>
        public void RemoveUser(IUser user)
        {
            this.RemoveUser(user, "None");
        }

        /// <summary>
        /// Attempts to remove the specified user from this channel with the specified reason
        /// </summary>
        /// <param name="user">The user to remove</param>
        /// <param name="reason">The reason the user is being removed</param>
        public void RemoveUser(IUser user, string reason)
        {
            if (this.Core != null)
            {
            }
        }

        /// <summary>
        /// Should be called by <see cref="Core"/> when a user leaves this channel
        /// </summary>
        /// <param name="user">The user that left the channel</param>
        /// <param name="sender">The <see cref="IUser"/> that caused the user to leave</param>
        /// <param name="reason">The reason for leaving</param>
        public void RemoveUserLocal(IUser sender, IUser user, string reason)
        {
            if (this.users.Remove(user.UserID))
            {
                if (this.UserLeft != null)
                    this.UserLeft.Invoke(sender, user, reason);
            }
        }

        /// <summary>
        /// Called from <see cref="Core"/> when a message for this channel has been received (Client-side)
        /// </summary>
        /// <param name="message">The message that was received</param>
        /// <param name="sender">The <see cref="IUser"/> that sent the message</param>
        public void OnMessage(BasicMessage message, IUser sender)
        {
            if (this.MessageReceived != null)
                this.MessageReceived.Invoke(message, sender);
        }

        /// <summary>
        /// Should be called by <see cref="Core"/> (client-side) when a user joined this channel
        /// </summary>
        /// <param name="user">The user that should be added</param>
        public void AddUserLocal(IUser user)
        {
            if (!this.users.ContainsKey(user.UserID))
            {
                this.users.Add(user.UserID, user);
                if (this.UserJoined != null)
                    this.UserJoined.Invoke(user);
            }
        }

        /// <summary>
        /// Sends the specified packet to this channel
        /// </summary>
        /// <param name="packet">The packet to send</param>
        public void Send(Packet packet)
        {
            if (this.Core != null)
            {
                packet.ChannelID = this.ChannelID;
                this.Core.Send(packet);
            }
        }

        /// <summary>
        /// Gets a reference to all the user currently residing in this channel
        /// </summary>
        public IEnumerable<IUser> Users
        {
            get { return this.users.Values; }
        }

        /// <summary>
        /// This event is fired whenever a user leaves this channel
        /// </summary>
        public event UserLeftEventHandler UserLeft;

        /// <summary>
        /// This delegate is used in the <see cref="RemoteChannel.UserLeft"/> event when a user leaves the channel
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="user">The user that left the channel</param>
        /// <param name="reason">The reason for leaving</param>
        public delegate void UserLeftEventHandler(IUser sender, IUser user, string reason);

        /// <summary>
        /// Fired whenever a user joins this channel
        /// </summary>
        public event UserEventHandler UserJoined;

        /// <summary>
        /// This event is fired whenever a message has been received
        /// </summary>
        public event Action<BasicMessage, IUser> MessageReceived;

        /// <summary>
        /// This event is fired whenever the channel is completely reloaded
        /// </summary>
        public event Action Reloaded;

        /// <summary>
        /// This event is fired whenever a user in this channel has been updated
        /// </summary>
        public event UserUpdatedHandler UserUpdated;

        /// <summary>
        /// Checks if the specified user is currently in this channel
        /// </summary>
        /// <param name="user">The user to check for</param>
        /// <returns>True if the user is currently in this channel, otherwise false</returns>
        public bool ContainsUser(IUser user)
        {
            if (user == null)
                return false;
            return this.users.ContainsKey(user.UserID);
        }
    }
}